/***************************************************************************
 *   Copyright (C) 2008 by Branan Riley   *
 *   branan@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "lwoloader.h"
#include <physfs.h>

#include <QtDebug>

// Enum for various 32-bit LWO constants
enum lwo32 {
  form_chunk = 0x464F524D,
  lwo2_chunk = 0x4C574F32,
  pnts_chunk = 0x504E5453,
  pols_chunk = 0x504F4C53,
  pols_face_chunk = 0x46414345,
};

// LWO constants
#define CHUNK_FORM 0x464F524D
#define CHUNK_LWO2 0x4C574F32
#define CHUNK_PNTS 0x504E5453
#define CHUNK_POLS 0x504F4C53
#define POLS_TYPE_FACE 0x46414345
#define POLS_VERTEX_COUNT_MASK 0x03FF


LwoLoader::LwoLoader(QObject *parent)
 : ModelLoader(parent)
{
}

LwoLoader::~LwoLoader()
{
}

bool LwoLoader::loadFile(const QString &filename)
{
  PHYSFS_file *file;

  file = PHYSFS_openRead(filename.toLocal8Bit().data());
  if(!checkFileType(file)) {
    PHYSFS_close(file);
    return false;
  }

  while(!PHYSFS_eof(file)) {
    PHYSFS_uint32 value;
    PHYSFS_readUBE32(file, &value);
    switch(value) {
      case CHUNK_PNTS:
        loadPntsChunk(file);
        break;
      case CHUNK_POLS:
        loadPolsChunk(file);
        break;
      default:
        skipChunk(file);
        break;
    }
  }
  return true;
}

bool LwoLoader::checkFileType(PHYSFS_file *file)
{
  PHYSFS_uint32 value;

  PHYSFS_readUBE32(file, &value);
  if(value != CHUNK_FORM) {
    qDebug("Not an LWO2 file: FORM not found");
    return false;
  }

  PHYSFS_seek(file, 8); // the 8th byte is the start of what should be the LWO2 chunk
  PHYSFS_readUBE32(file, &value);
  if(value != CHUNK_LWO2) {
    qDebug("Not an LWO2 file: LWO2 not found");
    return false;
  }
  return true;
}

void LwoLoader::loadPntsChunk(PHYSFS_file *file)
{
  qDebug("Entering loadPntsChunk");
  PHYSFS_uint32 chunk_size;

  PHYSFS_readUBE32(file, &chunk_size);
  _num_vert_elements = chunk_size/4;
  
  _vertices = new float[_num_vert_elements];
  PHYSFS_read(file, _vertices, 1, chunk_size);

  PHYSFS_uint32 *tmp = (PHYSFS_uint32*)(_vertices);
  for(unsigned int i = 0; i < chunk_size/4; i++) {
    tmp[i] = PHYSFS_swapUBE32(tmp[i]);
  }
}

void LwoLoader::loadPolsChunk(PHYSFS_file *file)
{
  qDebug("Entering loadPolsChunk");
  PHYSFS_uint32 chunk_size;
  PHYSFS_sint64 chunk_start;
  PHYSFS_uint32 value;
  QList<unsigned int> face_list;

  PHYSFS_readUBE32(file, &chunk_size);
  chunk_start = PHYSFS_tell(file);

  PHYSFS_readUBE32(file, &value);

  // Don't support any kind of object except plain old polygons
  if(value != POLS_TYPE_FACE) {
    qDebug("Not a FACE POLS chunk. Skipping");
    // skip to the end of the chunk
    PHYSFS_seek(file, chunk_start + chunk_size - 4);
    return;
  }

  // Keep reading polygons until we've hit the end of the chunk
  while(PHYSFS_tell(file) != chunk_start + chunk_size) {
    PHYSFS_uint16 numverts;
    PHYSFS_readUBE16(file, &numverts);
    numverts &= POLS_VERTEX_COUNT_MASK;
    qDebug() << "Reading a face with " << numverts << " vertices";
    // Skip any faces that don't have 3 vertices
    if(numverts == 3) {
      face_list.append(readVariableIndex(file));
      face_list.append(readVariableIndex(file));
      face_list.append(readVariableIndex(file));
    } else {
      for(PHYSFS_uint16 i = 0; i < numverts; i++) {
        readVariableIndex(file);
      }
    }
  }
  _num_indices = face_list.count();
  _indices = new unsigned int[_num_indices];
  for(unsigned int i = 0; i < _num_indices; i++) {
    _indices[i] = face_list[i];
  }
  if(PHYSFS_tell(file) %2) {
    PHYSFS_seek(file, PHYSFS_tell(file) +1);
  }

#ifndef QT_NO_DEBUG_OUTPUT
  for(int i = 0; i < face_list.count()/3; i++) {
    qDebug() << "Face " << i << ":" << face_list[3*i] << "," << face_list[3*i+1] << "," << face_list[3*i+2];
  }
#endif
}

PHYSFS_uint32 LwoLoader::readVariableIndex(PHYSFS_file *file)
{
  unsigned char first_byte;
  PHYSFS_uint32 index32;
  PHYSFS_uint16 index16;

  // Read the first byte of the index, then go back
  // no net change in file position
  PHYSFS_read(file, &first_byte, 1, 1);
  PHYSFS_seek(file, PHYSFS_tell(file)-1);

/*
#ifndef QT_NO_DEBUG_OUTPUT
  PHYSFS_readUBE16(file, &index16);
  qDebug() << "16-bit index: " << index16;

  PHYSFS_readUBE32(file, &index32);
  qDebug() << "32-bit index: " << index32;
#endif
*/

  if(first_byte == 0xFF) {
//    qDebug("Using 32-bit index");
    PHYSFS_readUBE32(file, &index32);
    // Ignore the first byte when it's equal to 0xFF
    index32 &= 0x00FFFFFF;
  } else {
//    qDebug("Using 16-bit index");
    PHYSFS_readUBE16(file, &index16);
    index32 = index16;
  }
  return index32;
}

void LwoLoader::skipChunk(PHYSFS_file *file)
{
  PHYSFS_uint32 chunk_length;
  PHYSFS_uint64 position;

  // Skip the code for getting the chunk ID if we're not going to display it anyway
#ifndef QT_NO_DEBUG_OUTPUT
  char chunk[5] = {0, 0, 0, 0, 0};
  position = PHYSFS_tell(file);

  PHYSFS_seek(file, position-4);

  PHYSFS_readULE32(file, (PHYSFS_uint32*)(&chunk));

  qDebug() << "Skipping chunk: " << chunk;
#endif

  PHYSFS_readUBE32(file, &chunk_length);
  if(chunk_length %2) {
    chunk_length++; // odd-size chunks are padded to be even-size
  }
  position = PHYSFS_tell(file);
  PHYSFS_seek(file, position+chunk_length);
}
